
#include <Liberty3D/Library/TaskScheduler.h>
#include <Liberty3D/Library/Task.h>
#include <Liberty3D/Library/Mutex/Local.h>

#ifdef WIN32
#pragma warning(disable : 4251)
#endif

#include <algorithm>
#include <boost/thread/condition.hpp>

using namespace Liberty::Engine;
using namespace Liberty::Library;

static boost::mutex              ConditionMutex;
static boost::condition_variable Condition;

boost::mutex                     TaskScheduler::_mutex;
volatile bool                    TaskScheduler::_running = true;
std::list<std::shared_ptr<Task>> TaskScheduler::_tasks;
std::list<std::shared_ptr<Task>> TaskScheduler::_runningTasks;

void TaskScheduler::init() {
}

void TaskScheduler::reorder() {
    _tasks.sort([] (const std::shared_ptr<Task>& t1, const std::shared_ptr<Task>& t2) -> bool {
        return t1->priority() < t2->priority();
    });
}

void TaskScheduler::scheduleLocked(const std::shared_ptr<Task>& task) {
    _tasks.push_back(task);
    reorder();
    task->_options &= ~TaskStatusBits;
    task->_options |= TaskScheduled;

    scheduleImpl(task); 
}

void TaskScheduler::schedule(const std::shared_ptr<Task>& task) {
    if (!task) {
        return;
    }

    {
        boost::mutex::scoped_lock guard(_mutex);

        scheduleLocked(task);
    }

    Condition.notify_all();
}

void TaskScheduler::cancel(const std::shared_ptr<Task>& task) {
    if (!task) {
        return;
    }

    {
        boost::mutex::scoped_lock guard(_mutex);

        std::remove(_tasks.begin(), _tasks.end(), task);
        cancelImpl(task);
    }

    Condition.notify_all();
}

std::shared_ptr<Task> TaskScheduler::dequeue(bool mainThread) {
    boost::mutex::scoped_lock guard(_mutex);

    if (_running) {
        auto it (_tasks.begin());
        auto end(_tasks.end());

        for (; it != end; ++it) {
            Task* taskPtr = (*it).get();

            if (taskPtr->isReady() && (taskPtr->mustExecuteOnMainThread() == mainThread)) {
                std::shared_ptr<Task> task(*it);

                _tasks.erase(it);
                taskPtr->_options &= ~TaskStatusBits;
                taskPtr->_options |= TaskRunning;
                _runningTasks.push_back(task);
                return task;
            }
        }
    }

    return std::shared_ptr<Task>();
}

void TaskScheduler::triggerTaskEvent() {
    Condition.notify_all();
}

void TaskScheduler::waitForTaskEvent(boost::mutex::scoped_lock& lock) {
    Condition.wait(lock);
}

bool TaskScheduler::hasPendingTasks() {
    boost::mutex::scoped_lock guard(_mutex);

    auto it (_tasks.begin());
    auto end(_tasks.end());

    for (; it != end; ++it) {
        std::shared_ptr<Task>& task(*it);

        if (task->_options & TaskBackground) {
            continue;
        }

        return true;
    }

    return _runningTasks.size() > 0;
}

bool TaskScheduler::waitForPendingTasks() {
    boost::mutex::scoped_lock guard(_mutex);

    while (_running) {
        auto it (_tasks.begin());
        auto end(_tasks.end());

        for (; it != end; ++it) {
            std::shared_ptr<Task>& task(*it);

            if (task->_options & TaskBackground) {
                continue;
            }

            return true;
        }

        if (_runningTasks.size() == 0) {
            break;
        }

        Condition.wait(guard);
    }

    return false;
}

bool TaskScheduler::pooling() {
    std::shared_ptr<Task> taskToRun(dequeue(false));

    if (taskToRun) {
        taskToRun->execute();
        finished(taskToRun);
        return true;
    }

    return false;
}

void TaskScheduler::finished(const std::shared_ptr<Task>& task) {
    boost::mutex::scoped_lock guard(_mutex);

    auto it(std::find(_runningTasks.begin(), _runningTasks.end(), task));

    if (it != _runningTasks.end()) {
        _runningTasks.erase(it);
    }

    if (task->_options & TaskRecurring) {
        scheduleLocked(task);
    }

    Condition.notify_all();
}
